feat: enable server-side Blaze template rendering (foundation for SSG/SSR)#507
Open
dupontbertrand wants to merge 8 commits intometeor:release-3.1.0from
Open
feat: enable server-side Blaze template rendering (foundation for SSG/SSR)#507dupontbertrand wants to merge 8 commits intometeor:release-3.1.0from
dupontbertrand wants to merge 8 commits intometeor:release-3.1.0from
Conversation
…for all architectures This allows compiled Blaze templates to be available on the server, which is required for server-side rendering use cases (SSG/SSR). Server-side overhead is minimal (~500 bytes per template). The <template> tag compiled output is pure JavaScript with no DOM dependencies. <body> tag compilation is handled separately in templating-tools.
The <body> tag compiled code calls Template.body.renderToDocument which uses document.body — this crashes on the server. Wrap generateBodyJS() output with 'if (Meteor.isClient)' to make body compiled code safe to load on both architectures. generateTemplateJS() is unchanged — the <template> tag compiled output (Template.__checkName + Template[name] = new Template(...)) is already server-safe.
The generated code calls document.body.setAttribute() which crashes on the server. Wrap it in 'if (Meteor.isClient)' to make the compiled HTML safe to load on both architectures.
…teor.isClient Export Template globally (both client and server) so that compiled templates can register themselves server-side. Previously Template was client-only, which prevented any server-side rendering use case. Template.__checkName is already server-safe (no DOM). Template.body, Template.__pendingReplacement, Template._applyHmrChanges, and the HMR branch of Template._migrateTemplate all require the DOM — wrapped with 'if (Meteor.isClient)'. dynamic.html and dynamic.js remain client-only (they require DOM).
Matches the export change in templating-runtime. The templating package re-exports Template from templating-runtime and was also restricting it to the client — which would override the templating-runtime change.
…or.isClient wrap The simpleBody helper in html-scanner-tests now expects the <body> rendering code to be wrapped in 'if (Meteor.isClient)', matching the change in generateBodyJS().
Document the new server-side Blaze rendering capability: - Blaze.toHTML() / Blaze.toHTMLWithData() work on the server - How to make templates available server-side (import in server/main.js) - Template restrictions (no Session, no this.subscribe, no Template.dynamic) - Link to static-render package for higher-level SSG/SSR API - Manual integration with server-render package - Known rspack limitation
dupontbertrand
added a commit
to dupontbertrand/meteor
that referenced
this pull request
Apr 20, 2026
static-render is a server-side orchestration package that pre-renders Blaze routes as HTML for SEO. Two modes: - SSG (Static Site Generation): routes with static: 'ssg' are rendered once at server startup and cached permanently. Good for pages that rarely change (about, contact, terms). - SSR (Server-Side Rendering): routes with static: 'ssr' are rendered on-the-fly at each request with fresh data from MongoDB via async staticData(). Good for product pages, articles, profiles. Pre-rendered HTML is injected into the Meteor boilerplate via req.dynamicBody and req.dynamicHead, so the client-side app still loads and takes over normally (server pre-render + client takeover, not React-style hydration). The package auto-discovers routes from flow-router-extra (weak dep). It also provides graceful error handling: if a template crashes during rendering, a placeholder comment is rendered and the page falls back to client-side rendering. Requires the Blaze 3.1.x+ server-rendering changes (meteor/blaze#507). Includes: - packages/static-render/package.js, static-render-server.js, README.md - v3-docs/docs/packages/static-render.md (full API docs) - Sidebar entry in v3-docs/.vitepress/config.mts Forum discussion: https://forums.meteor.com/t/ssg-ssr-for-meteor-blaze-a-proof-of-concept/64556
- Add dedicated SSG subsection with about-page example - Add dedicated SSR subsection with product-page example - Add SSG vs SSR comparison table for decision-making
Collaborator
|
I would target this for release 3.2.0 to keep 3.1.0 manageable and finish it soon |
Author
|
As you want boss, to be honest it's a "small" changes but if you prefer to wait for 3.2 it's up to you, maybe you have to check with someone else because this PR have to be merged at the same time at this one meteor/meteor#14349 |
dupontbertrand
added a commit
to dupontbertrand/meteor
that referenced
this pull request
Apr 20, 2026
static-render is a server-side orchestration package that pre-renders Blaze routes as HTML for SEO. Two modes: - SSG (Static Site Generation): routes with static: 'ssg' are rendered once at server startup and cached permanently. Good for pages that rarely change (about, contact, terms). - SSR (Server-Side Rendering): routes with static: 'ssr' are rendered on-the-fly at each request with fresh data from MongoDB via async staticData(). Good for product pages, articles, profiles. Pre-rendered HTML is injected into the Meteor boilerplate via req.dynamicBody and req.dynamicHead, so the client-side app still loads and takes over normally (server pre-render + client takeover, not React-style hydration). The package auto-discovers routes from flow-router-extra (weak dep). It also provides graceful error handling: if a template crashes during rendering, a placeholder comment is rendered and the page falls back to client-side rendering. Requires the Blaze 3.1.x+ server-rendering changes (meteor/blaze#507). Includes: - packages/static-render/package.js, static-render-server.js, README.md - v3-docs/docs/packages/static-render.md (full API docs) - Sidebar entry in v3-docs/.vitepress/config.mts Forum discussion: https://forums.meteor.com/t/ssg-ssr-for-meteor-blaze-a-proof-of-concept/64556
dupontbertrand
added a commit
to dupontbertrand/meteor
that referenced
this pull request
Apr 20, 2026
static-render is a server-side orchestration package that pre-renders Blaze routes as HTML for SEO. Two modes: - SSG (Static Site Generation): routes with static: 'ssg' are rendered once at server startup and cached permanently. Good for pages that rarely change (about, contact, terms). - SSR (Server-Side Rendering): routes with static: 'ssr' are rendered on-the-fly at each request with fresh data from MongoDB via async staticData(). Good for product pages, articles, profiles. Pre-rendered HTML is injected into the Meteor boilerplate via req.dynamicBody and req.dynamicHead, so the client-side app still loads and takes over normally (server pre-render + client takeover, not React-style hydration). The package auto-discovers routes from flow-router-extra (weak dep). It also provides graceful error handling: if a template crashes during rendering, a placeholder comment is rendered and the page falls back to client-side rendering. Requires the Blaze 3.1.x+ server-rendering changes (meteor/blaze#507). Includes: - packages/static-render/package.js, static-render-server.js, README.md - v3-docs/docs/packages/static-render.md (full API docs) - Sidebar entry in v3-docs/.vitepress/config.mts Forum discussion: https://forums.meteor.com/t/ssg-ssr-for-meteor-blaze-a-proof-of-concept/64556
dupontbertrand
added a commit
to dupontbertrand/meteor
that referenced
this pull request
Apr 20, 2026
static-render is a server-side orchestration package that pre-renders Blaze routes as HTML for SEO. Two modes: - SSG (Static Site Generation): routes with static: 'ssg' are rendered once at server startup and cached permanently. Good for pages that rarely change (about, contact, terms). - SSR (Server-Side Rendering): routes with static: 'ssr' are rendered on-the-fly at each request with fresh data from MongoDB via async staticData(). Good for product pages, articles, profiles. Pre-rendered HTML is injected into the Meteor boilerplate via req.dynamicBody and req.dynamicHead, so the client-side app still loads and takes over normally (server pre-render + client takeover, not React-style hydration). The package auto-discovers routes from flow-router-extra (weak dep). It also provides graceful error handling: if a template crashes during rendering, a placeholder comment is rendered and the page falls back to client-side rendering. Requires the Blaze 3.1.x+ server-rendering changes (meteor/blaze#507). Includes: - packages/static-render/package.js, static-render-server.js, README.md - v3-docs/docs/packages/static-render.md (full API docs) - Sidebar entry in v3-docs/.vitepress/config.mts Forum discussion: https://forums.meteor.com/t/ssg-ssr-for-meteor-blaze-a-proof-of-concept/64556
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enables
Blaze.toHTML()to work on the server by making compiled templates and theTemplateregistry available server-side. This is the foundation for SSG/SSR use cases (demonstrated in a working POC — see links below).The key insight is that
Blaze.toHTML()already works server-side through a DOM-free code path — the only problem was that compiled templates and theTemplateglobal were restricted to the client. This PR lifts that restriction without breaking anything client-side.Changes
5 small, backward-compatible changes across 5 packages:
templating-compilerarchMatching: 'web'so templates compile for all architecturestemplating-toolsgenerateBodyJS()withMeteor.isClient— body rendering usesdocument.bodycaching-html-compilerMeteor.isClienttemplating-runtimeTemplateto server, guard DOM code withMeteor.isClienttemplatingTemplateto server (matchestemplating-runtime)Why this is safe
<template>tag compiled output is pure JavaScript (Template.__checkName+Template[name] = new Template(...)). No DOM access.<body>tag compiled output usesdocument.body— wrapped inMeteor.isClientto stay client-only.Template.__checkName,Template._migrateTemplate,Blaze.Templateconstructor are already server-safe (no DOM dependencies).Template.body, HMR,_applyHmrChanges) wrapped inMeteor.isClientto remain client-only.What this does NOT change
blaze,htmljs,spacebarscores are unchangedmaterializer,domrange,dombackend,events,attrs) stays client-onlyProof of concept
Built on top of these changes:
@meteorjs/rspack— separate PR)Forum discussion: https://forums.meteor.com/t/ssg-ssr-for-meteor-blaze-a-proof-of-concept/64556
Testing done
Template.myTemplateis now accessible server-side afterimport '../lib/templates.html'inserver/main.jsBlaze.toHTML(Template.myTemplate)returns proper HTML strings server-sideBlaze.toHTMLWithData(Template.myTemplate, data)works with data contexts#each,#if,#unlessrender correctly through_expandView(forExpansion=true)meteor buildsucceedsConsumer-side usage
Apps that want to use server-side rendering can now do:
Templates must be written as pure render functions against explicit data — no
Session, nothis.subscribe(), noTemplate.dynamic(which remains client-only).